package com.pbd.server;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.engine.transaction.IsolatedWork;
import org.hibernate.engine.transaction.Isolater;
import org.hibernate.exception.JDBCExceptionHelper;
import org.hibernate.id.IdentifierGenerationException;

// import org.hibernate.id.IdentifierGeneratorFactory;
import org.hibernate.id.IdentifierGeneratorHelper;


import org.hibernate.id.TableGenerator;
import org.hibernate.mapping.Table;
import org.hibernate.type.Type;
import org.hibernate.util.PropertiesHelper;

public class HiloGenerator extends TableGenerator {

	public static final String MAX_LO = "max_lo";

	private long hi;
	private int lo;
	private int maxLo;
	private Class returnClass;

	public static final String COLUMN = "column";
	public static final String FILTER = "filter";

	/** Default column name */
	public static final String DEFAULT_COLUMN_NAME = "next_hi";
	public static final String DEFAULT_FILTER_NAME = "next_val_filter";

	/** The table parameter */
	public static final String TABLE = "table";

	/** Default table name */
	public static final String DEFAULT_TABLE_NAME = "hibernate_unique_key";

	private String tableName;
	private String columnName;
	private String filterName;
	private String query;
	private String update;
	private String filterValue;

	public void configure(Type type, Properties params, Dialect dialect) {
		tableName = PropertiesHelper.getString(TABLE, params, DEFAULT_TABLE_NAME);
		columnName = PropertiesHelper.getString(COLUMN, params, DEFAULT_COLUMN_NAME);
		filterName = PropertiesHelper.getString(FILTER, params, DEFAULT_FILTER_NAME);
		String schemaName = params.getProperty(SCHEMA);
		String catalogName = params.getProperty(CATALOG);

		if (tableName.indexOf('.') < 0)
			tableName = Table.qualify(catalogName, schemaName, tableName);
		if (filterName != null && filterName.trim().length() > 0) {
			query = "select " + columnName + " from " + dialect.appendLockHint(LockMode.UPGRADE, tableName) + dialect.getForUpdateString();
			update = "update " + tableName + " set " + columnName + " = ? where " + columnName + " = ? AND " + filterName + "= ?";
		}
		returnClass = type.getReturnedClass();

	}

	public Object generatorKey() {
		return tableName;
	}

	public Serializable doWorkInNewTransaction(final SessionImplementor session) throws HibernateException {
		class Work implements IsolatedWork {
			Serializable generatedValue;

			public void doWork(Connection connection) throws HibernateException {
				String sql = null;
				try {
					generatedValue = doWorkInCurrentTransaction(connection, sql);
				} catch (SQLException sqle) {
					throw JDBCExceptionHelper.convert(session.getFactory().getSQLExceptionConverter(), sqle, "could not get or update next value", sql);
				}
			}
		}
		Work work = new Work();
		Isolater.doIsolatedWork(work, session);
		return work.generatedValue;
	}

	public synchronized Serializable generate(SessionImplementor session, Object obj) throws HibernateException {
		String clazzName = obj.getClass().getName();
		filterValue = clazzName.substring(clazzName.lastIndexOf(".") + 1);
		long hival = ((Long) doWorkInNewTransaction(session)).longValue();
		lo = (hival == 0) ? 1 : 0;
		hi = hival * (maxLo + 1);
				
		Number dataValue = IdentifierGeneratorHelper.createNumber(hi + lo++, returnClass);
		Long returnObject = new Long(dataValue.longValue());
		return returnObject;
	}

	public Serializable doWorkInCurrentTransaction(Connection conn, String sql) throws SQLException {
		long result;
		int rows;
		do {
			String forUpdate = query.substring(query.indexOf("for"));
			String queryStr = query.substring(0, query.indexOf("for"));
			queryStr = queryStr + " WHERE " + filterName + " = '" + filterValue + "'" + forUpdate;
			sql = queryStr;
			PreparedStatement qps = conn.prepareStatement(queryStr);
			try {
				ResultSet rs = qps.executeQuery();
				if (!rs.next()) {
					String err = "could not read a hi value - you need to populate the table:" + tableName;
					throw new IdentifierGenerationException(err);
				}
				result = rs.getLong(1);
				rs.close();
			} catch (SQLException sqle) {
				throw sqle;
			} finally {
				qps.close();
			}

			sql = update;
			PreparedStatement ups = conn.prepareStatement(update);
			try {
				ups.setLong(1, result + 1);
				ups.setLong(2, result);
				ups.setString(3, filterValue);
				rows = ups.executeUpdate();
			} catch (SQLException sqle) {
				throw sqle;
			} finally {
				ups.close();
			}
		} while (rows == 0);
		return new Long(result + 1);
	}
}